package com.example.codegen.vo;

import cn.hutool.core.util.StrUtil;
import com.example.codegen.common.ObjectCopier;
import com.example.codegen.model.Column;
import com.example.codegen.model.GenerationStrategy;
import com.example.codegen.model.Table;
import com.mysql.cj.MysqlType;
import lombok.Getter;
import org.apache.ibatis.type.JdbcType;
import org.postgresql.jdbc.TypeInfoCache;

import java.io.File;
import java.sql.JDBCType;
import java.util.List;

public class GeneratedBuilder {

    private GenerationStrategy strategy;

    private String databaseDriver;

    @Getter
    private File javaGenerateDirectory;

    @Getter
    private File resourceGenerateDirectory;

    private TypeInfoCache postgresType = new TypeInfoCache(null, Integer.MAX_VALUE);

    public GeneratedBuilder(GenerationStrategy strategy, String databaseDriver) {
        this.strategy = strategy;
        this.databaseDriver = databaseDriver;
        this.javaGenerateDirectory = createDirectory(strategy.getProjectPath(), strategy.getJavaPath(), strategy.getJavaPackage().replaceAll("\\.", "/"));
        this.resourceGenerateDirectory = createDirectory(strategy.getProjectPath(), strategy.getResourcePath());
    }

    private File createDirectory(String rootPath, String... paths) {
        File file = new File(rootPath);
        if (!file.exists()) {
            file.mkdirs();
        }
        for (String path : paths) {
            file = new File(file, path);
            if (!file.exists()) {
                file.mkdirs();
            }
        }
        return file;
    }

    public GeneratedTable build(Table table, List<Column> columns) {
        GeneratedTable generatedTable = ObjectCopier.INSTANCE.toGeneratedTable(table);
        doInitTable(generatedTable);
        for (Column column : columns) {
            GeneratedColumn generatedColumn = ObjectCopier.INSTANCE.toGeneratedColumn(column);
            doInitColumn(generatedColumn);
            generatedTable.getColumns().add(generatedColumn);
        }
        return generatedTable;
    }

    private void doInitTable(GeneratedTable generatedTable) {
        if (StrUtil.isNotBlank(strategy.getModelName())) {
            generatedTable.setModelName(strategy.getModelName());
        } else {
            boolean initialized = false;
            for (String prefix : strategy.getTableNameOverridePrefixes()) {
                if (generatedTable.getTableName().startsWith(prefix)) {
                    generatedTable.setModelName(StrUtil.upperFirst(StrUtil.toCamelCase(generatedTable.getTableName().substring(prefix.length()))));
                    initialized = true;
                    break;
                }
            }
            if (!initialized) {
                generatedTable.setModelName(StrUtil.upperFirst(StrUtil.toCamelCase(generatedTable.getTableName().toLowerCase())));
            }
        }
        generatedTable.setJavaPackage(strategy.getJavaPackage());
        generatedTable.setAuthor(strategy.getAuthor());
        generatedTable.setVersion(strategy.getVersion());
    }

    private void doInitColumn(GeneratedColumn generatedColumn) {
        boolean initialized = false;
        for (String prefix : strategy.getColumnNameOverridePrefixes()) {
            if (generatedColumn.getColumnName().startsWith(prefix)) {
                generatedColumn.setFieldName(StrUtil.toCamelCase(generatedColumn.getColumnName().substring(prefix.length())));
                initialized = true;
            }
        }
        if (!initialized) {
            generatedColumn.setFieldName(StrUtil.toCamelCase(generatedColumn.getColumnName().toLowerCase()));
        }
        // 设置java类型
        switch (databaseDriver) {
            case "com.mysql.cj.jdbc.Driver":
            case "com.mysql.jdbc.Driver":
            case "org.gjt.mm.mysql.Driver":
                try {
                    MysqlType mysqlType = MysqlType.getByName(generatedColumn.getDataType());
                    String fieldType = mysqlType.getClassName();
                    generatedColumn.setFieldType("[B".equals(fieldType) ? "byte[]" : fieldType);
                    int sqlType = mysqlType.getJdbcType();
                    JdbcType jdbcType = JdbcType.forCode(sqlType);
                    generatedColumn.setJdbcType(jdbcType.name());
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
                break;
            case "org.postgresql.Driver":
                try {
                    int oid = postgresType.getPGType(generatedColumn.getDataType());
                    String fieldType = postgresType.getJavaClass(oid);
                    generatedColumn.setFieldType("[B".equals(fieldType) ? "byte[]" : fieldType);
                    int sqlType = postgresType.getSQLType(oid);
                    JdbcType jdbcType = JdbcType.forCode(sqlType);
                    generatedColumn.setJdbcType(jdbcType.name());
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
                break;
            case "oracle.jdbc.driver.OracleDriver":
                try {
                    String dataType = generatedColumn.getDataType().toUpperCase();
                    dataType = dataType.replaceAll("\\([^()]*\\)", "").replaceAll(" ", "_");
                    generatedColumn.setDataType(dataType);
                    String fieldType = null;
                    JDBCType jdbcType = null;
                    switch (dataType) {
                        case "CHAR":
                            fieldType = "java.lang.String";
                            jdbcType = JDBCType.valueOf("CHAR");
                            break;
                        case "VARCHAR":
                        case "VARCHAR2":
                            fieldType = "java.lang.String";
                            jdbcType = JDBCType.valueOf("VARCHAR");
                            break;
                        case "NCHAR":
                        case "NVARCHAR":
                        case "NVARCHAR2":
                            fieldType = "java.lang.String";
                            jdbcType = JDBCType.valueOf("NCHAR");
                            break;
                        case "LONG":
                            fieldType = "java.lang.String";
                            jdbcType = JDBCType.valueOf("LONGVARCHAR");
                            break;
                        case "NUMBER":
                        case "NUMERIC":
                        case "DECIMAL":
                            if (generatedColumn.getNumericPrecision() != null && generatedColumn.getNumericScale() != null && generatedColumn.getNumericScale() == 0) {
                                if (generatedColumn.getNumericPrecision() <= 9) {
                                    fieldType = "java.lang.Integer";
                                    jdbcType = JDBCType.valueOf("INTEGER");
                                } else if (generatedColumn.getNumericPrecision() <= 18) {
                                    fieldType = "java.lang.Long";
                                    jdbcType = JDBCType.valueOf("BIGINT");
                                } else {
                                    fieldType = "java.math.BigDecimal";
                                    jdbcType = JDBCType.valueOf("DECIMAL");
                                }
                            }
                            fieldType = "java.math.BigDecimal";
                            jdbcType = JDBCType.valueOf("DECIMAL");
                            break;
                        case "RAW":
                            fieldType = "byte[]";
                            jdbcType = JDBCType.valueOf("BINARY");
                            break;
                        case "LONGRAW":
                            fieldType = "byte[]";
                            jdbcType = JDBCType.valueOf("LONGVARBINARY");
                            break;
                        case "DATE":
                            fieldType = "java.sql.Date";
                            jdbcType = JDBCType.valueOf("DATE");
                            break;
                        case "TIMESTAMP":
                            fieldType = "java.sql.Timestamp";
                            jdbcType = JDBCType.valueOf("TIMESTAMP");
                            break;
                        case "TIMESTAMP_WITH_TIME_ZONE":
                            fieldType = "oracle.sql.TIMESTAMPTZ";
                            jdbcType = JDBCType.valueOf("TIMESTAMP_WITH_TIMEZONE");
                            break;
                        case "TIMESTAMP_WITH_LOCAL_TIME_ZONE":
                            fieldType = "oracle.sql.TIMESTAMPLTZ";
                            jdbcType = JDBCType.valueOf("TIMESTAMP_WITH_TIMEZONE");
                            break;
                        case "FLOAT":
                        case "BINARY_FLOAT":
                            fieldType = "java.lang.Float";
                            jdbcType = JDBCType.valueOf("FLOAT");
                            break;
                        case "DOUBLE":
                        case "BINARY_DOUBLE":
                            fieldType = "java.lang.Double";
                            jdbcType = JDBCType.valueOf("DOUBLE");
                            break;
                        case "INTERVAL_DAY_TO_SECOND":
                            fieldType = "oracle.sql.INTERVALDS";
                            jdbcType = JDBCType.valueOf("OTHER");
                            break;
                        case "INTERVAL_YEAR_TO_MONTH":
                            fieldType = "oracle.sql.INTERVALYM";
                            jdbcType = JDBCType.valueOf("OTHER");
                            break;
                        case "ROWID":
                            fieldType = "oracle.sql.ROWID";
                            jdbcType = JDBCType.valueOf("ROWID");
                            break;
                        case "BLOB":
                            fieldType = "oracle.jdbc.OracleBlob";
                            jdbcType = JDBCType.valueOf("BLOB");
                            break;
                        case "CLOB":
                            fieldType = "oracle.jdbc.OracleClob";
                            jdbcType = JDBCType.valueOf("CLOB");
                            break;
                        case "NCLOB":
                            fieldType = "java.sql.NClob";
                            jdbcType = JDBCType.valueOf("NCLOB");
                            break;
                        case "BFILE":
                            fieldType = "oracle.sql.BFILE";
                            jdbcType = JDBCType.valueOf("OTHER");
                            break;
                        default:
                            throw new RuntimeException("oracle不支持的数据类型：" + dataType);
                    }
                    generatedColumn.setFieldType(fieldType);
                    int sqlType = jdbcType.getVendorTypeNumber();
                    JdbcType mybatisType = JdbcType.forCode(sqlType);
                    generatedColumn.setJdbcType(mybatisType.name());
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
                // TODO:
                break;
            case "com.microsoft.sqlserver.jdbc.SQLServerDriver":
                // TODO:
                break;
            case "org.sqlite.JDBC":
                try {
                    String dataType = generatedColumn.getDataType().toUpperCase();
                    switch (dataType) {
                        case "INTEGER":
                            generatedColumn.setFieldType("java.lang.Integer");
                            generatedColumn.setJdbcType(JdbcType.INTEGER.name());
                            break;
                        case "TEXT":
                            generatedColumn.setFieldType("java.lang.String");
                            generatedColumn.setJdbcType(JdbcType.VARCHAR.name());
                            break;
                        case "REAL":
                            generatedColumn.setFieldType("java.lang.Double");
                            generatedColumn.setJdbcType(JdbcType.DOUBLE.name());
                            break;
                        case "BLOB":
                            generatedColumn.setFieldType("byte[]");
                            generatedColumn.setJdbcType(JdbcType.BLOB.name());
                            break;
                        default:
                            throw new RuntimeException("sqlite不支持的数据类型：" + dataType);
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
                break;
            default:
                throw new RuntimeException("不支持的数据库类型");
        }
    }
}
